/**
 * @particulars 对数据进行监听观察
 * 观察者模式：
 *   每个属性都有一个Dep，同时对象也有一个Dep（属性是被观察者）
 *   Watcher 就是观察者（属性变化了，就会通知观察者更新视图）
 */

import { popTarget, pushTarget } from "./dep";
import { queueWatcher } from "./queue";

let id = 0; // 标识Watcher，便于各自维护

// 1、不同的组件都有各自的Watcher，实现组件的局部更新
class Watcher {
  constructor(vm, exprOrFn, options, cb, userOptions = {}) {
    this.id = id++;
    // options 配置选项，是否是一个渲染Watcher =>（true）、是否是用户配置的watch、计算属性的Watcher =>（{...配置项}）
    this.renderWatcher = options;

    // exprOrFn 为string 表示的是用户监听的watch 属性，且含有user 则是针对于用户定义的watch，直接通过vm[exprOrFn] 取得watch 监听的值
    if (typeof exprOrFn === 'string' && this.renderWatcher.user) {
      this.getter = function () {
        // watch 里面监听的属性，拿到监听属性的值（会被代理）
        return vm[exprOrFn];
      }.bind(vm);
    } else {
      // 否则就是计算属性的Watcher => 计算属性函数/计算属性的get 函数 、 渲染的Watcher => updateComponent 渲染函数vm._update
      // 将回调函数绑定到Watcher上，调用getter 会从vm 实例上进行取值操作（渲染Watcher、计算属性Watcher）
      this.getter = exprOrFn.bind(vm);
    }

    // Watcher 记录对应的Dep，实现计算属性及卸载清空
    this.deps = [];
    // Set 结构，用于去重
    this.depsId = new Set();

    // 标识是否为用户定义的watch
    this.user = options.user;
    // TODO: 标识用户watch 深度监听
    this.deep = userOptions.deep;
    // 标识用户watch 立即执行一次
    this.immediate = userOptions.immediate;
    // 用户定义的watch 的执行函数
    if (this.user) this.cb = cb;

    // 标识计算属性是否要缓存，如果是渲染Watcher，this.lazy 为undefined
    this.lazy = options.lazy;
    // 标识是否为脏值，脏值则需要更新
    this.dirty = this.lazy;

    // 初始化渲染及更新操作，渲染Watcher 需要进行初始化渲染；
    // 计算属性的Watcher 不进行更新，本质上是由渲染Watcher 进行的重渲染
    if (this.renderWatcher === true) {
      // 当前为渲染函数
      this.get();
    } else if (this.renderWatcher.user) {
      // 当前为用户定义的watch，需要先存储一份值为旧值
      this.value = this.get();
    }

    // 用户定义的watch 设置了immediate: true 则立即执行一次watch，并将旧值传入作为最新值
    if (this.immediate) this.cb.call(vm, this.value);
  }

  // 计算属性中脏值需要更新
  evaluate() {
    // 计算属性中，获取到用户函数的返回值（计算属性读取的值就是this.value），
    // 并且还要标识为非脏值，避免重复执行计算属性，做到缓存效果
    this.value = this.get();
    this.dirty = false;
  }

  // 获取到最新值，同时Watcher 进栈Dep 收集对应的Watcher 再出栈
  get() {
    // 1-1、给Dep添加一个静态属性，收集对应的Watcher，Dep.target = this = Watcher
    pushTarget(this);
    // 1-2、渲染根实例，会从vm 上取值；针对于计算属性，将用户函数的结果返回作为最新值
    const value = this.getter();
    // 1-3、渲染完毕就清空，出栈，Dep.target 重置为栈内最后的Watcher
    popTarget();

    return value;
  }

  // Watcher 的依赖收集，收集对应的Dep
  depend() {
    // 让计算属性Watcher 也收集渲染Watcher，依靠渲染Watcher 进行响应式更新
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }

  // 记录Watcher 对应的Dep，重复的属性不用重复记录
  addDep(dep) {
    const id = dep.id;

    // 没有就收集对应的Dep
    if (!this.depsId.has(id)) {
      this.deps.push(dep);
      this.depsId.add(id);

      // 建立双向关系，Watcher 记住了Dep，同时Dep 也记录了Watcher
      dep.addSub(this);
    }
  }

  // 重新渲染，更新视图，this.lazy 说明是计算属性Watcher
  // 视图更新本质上是调用渲染Watcher 的this.getter 上存储的渲染函数vm._update
  update() {
    if (this.lazy) {
      // 如果是计算属性，依赖的值改变了。就需要标识为脏值进行更新
      this.dirty = true;
    } else {
      // 把当前的Watcher 暂存起来维护到队列里，进行异步更新
      queueWatcher(this);
    }
  }

  // 更新值并重渲染
  run() {
    /**
     * update 执行后，会把修改的操作维护到异步队列里面，然后一次性会执行this.run，
     * this.run 再会去调用this.get，
     * this.get 会去调用执行this.getter 上更新视图的渲染函数vm._update， 最后实现视图响应式更新，
     * 如果是用户自定义的watch 或者是计算属性Watcher，this.getter 则会拿到更新后的状态并返回结果
     */

    // 用户watch 回调的旧值
    const oldValue = this.value;
    // 拿到新值同时更新旧值
    const newValue = this.value = this.get();

    // 如果是用户定义的watch，属性变化了，需要执行watch 的回调函数，并传入最新值和旧值
    if (this.user) this.cb.call(vm, newValue, oldValue);
  }
}

export default Watcher